﻿using System;
using System.Timers;
using ErgoBreak.Data;

namespace ErgoBreak.Core
{
    internal sealed class BreakManager : IBreakManager, IDisposable
    {
        #region Fields
        private Timer snoozeTimer = null;
        private IBreakAction action;
        private Break currentBreak = null;
        private readonly int MINUTE_TO_MILISECONDS = 60000;
        #endregion

        #region Events
        /// <summary>
        /// Happens when a break is started.
        /// </summary>
        public event EventHandler BreakStarted;

        /// <summary>
        /// Happens when a break is ended.
        /// </summary>
        public event EventHandler BreakEnded;

        /// <summary>
        /// Occurs when a break is canceled.
        /// </summary>
        public event EventHandler BreakCanceled;

        /// <summary>
        /// Occurs when a break is snoozed. 
        /// </summary>
        public event EventHandler Snoozed;

        /// <summary>
        /// Occurs when a break snooze is over
        /// </summary>
        public event EventHandler SnoozeElapsed;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a BreakManager object.
        /// </summary>
        /// <param name="timer">Defines a snooze timer.</param>
        public BreakManager(Timer timer)
        {
            this.snoozeTimer = timer;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the current 'Break' instance.
        /// </summary>
        public Break CurrentBreak
        {
            get { return this.currentBreak; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Starts a break lasting for the given time.
        /// </summary>
        /// <param name="breakDuration">Defines how long the break should last in minutes.</param>
        public void StartBreak(Break breakObject, IBreakAction breakAction)
        {
            this.currentBreak = breakObject;
            // carry out the break action
            this.action = breakAction;
            action.Perform();
            OnBreakStarted();
        }

        /// <summary>
        /// Postpones the current break for the given time.
        /// </summary>
        /// <param name="delayTime">Defines how long the break should be delayed in minutes.</param>
        public void Snooze(int delayTime)
        {
            // check if snoozing is possible
            if ( (this.currentBreak.Status != BreakStatus.Proposed) || (this.currentBreak.Status != BreakStatus.InProgress) )
                throw new InvalidOperationException(Messages.GetMessage("Err201"));

            // set snooze timer
            if (this.snoozeTimer == null)
                snoozeTimer = new Timer(delayTime * MINUTE_TO_MILISECONDS);
            snoozeTimer.Elapsed += new ElapsedEventHandler(ManageSnooze);

            // stop ongoing break 
            if (currentBreak.Status == BreakStatus.InProgress)
                StopBreak();

            // start snooze
            snoozeTimer.Start();
            OnSnoozed();
        }

        /// <summary>
        /// Cancels the current break
        /// </summary>
        public void CancelBreak()
        {
            // check if snoozing is possible
            if ((this.currentBreak.Status != BreakStatus.Proposed) || (this.currentBreak.Status != BreakStatus.InProgress))
                throw new InvalidOperationException(Messages.GetMessage("Err201"));
                
            StopBreak();
            OnBreakCanceled();
        }

        /// <summary>
        /// Releases resources used by BreakManager class.
        /// </summary>
        public void Dispose()
        {
            CleanupSnooze();
            CleanupBreakAction();
        }
        #endregion

        #region Non-Public Methods
        private void CleanupSnooze()
        {
            if (this.snoozeTimer != null)
            {
                snoozeTimer.Close();
                snoozeTimer.Dispose();
                snoozeTimer = null;
            }
        }

        private void CleanupBreakAction()
        {
            if (this.action is IDisposable)
                ((IDisposable)action).Dispose();

            action = null;
        }

        private void StopBreak()
        {
            CleanupBreakAction();
        }

        private void OnBreakEnded()
        {
            UpdateStatus(BreakStatus.Over);

            // raise the event
            if (this.BreakEnded != null)
            {
                BreakEnded(this, new EventArgs());
                //CleanupSnooze();
            }
        }

        private void OnBreakStarted()
        {
            UpdateStatus(BreakStatus.InProgress);

            // raise the event
            if (this.BreakStarted != null)
                BreakStarted(this, new EventArgs());
        }

        private void OnBreakCanceled()
        {
            UpdateStatus(BreakStatus.Canceled);

            // raise the event
            if (this.BreakCanceled != null)
                BreakCanceled(this, new EventArgs());
        }

        private void OnSnoozed()
        {
            UpdateStatus(BreakStatus.Snoozed);

            // raise the event
            if (this.Snoozed != null)
                Snoozed(this, new EventArgs());

        }

        private void OnSnoozeElapsed()
        {
            // raise the event
            if (this.SnoozeElapsed != null)
            {
                SnoozeElapsed(this, new EventArgs());
                //CleanupSnooze();
            }
        }

        private void ManageSnooze(object sender, ElapsedEventArgs e)
        {
            // stop the timer
            this.snoozeTimer.Stop();
            // raise the event
            OnSnoozeElapsed();
                
            // start the break again
            StartBreak(this.currentBreak, this.action);
        }

        private void UpdateStatus(BreakStatus breakStatus)
        {
            this.currentBreak.Status = breakStatus;
        }
        #endregion
    }
}
